#include "graph.h"
#include <QPalette>
#include <QDebug>
#include <QPainter>
#include <QMessageBox>
#include <QtGlobal>
#include <QTime>
#include <QDateTime>
#include <QFile>
#include <QFileDialog>
Graph::Graph(QWidget *parent ,int num) : QWidget(parent)
{
    /*设置窗口相关属性*/
    setAutoFillBackground(true);
    setPalette(Qt::white);
    resize(1200,400);
    setMinimumSize(1500,800);
    setMaximumSize(1500,800);
    this->num=num;
    /*添加窗口组件*/
    stateLabel.setParent(this);
    fileName.setParent(this);
    saveFileButton=new QPushButton("保存数据");
    fileNameInput=new QLineEdit;
    saveFileButton->setParent(this);
    fileNameInput->setParent(this);
    //stateLabel=new QLabel(this);
    fileName.setText("文件名：");
    fileName.setParent(this);
    QDateTime * dateTime=new QDateTime(QDateTime::currentDateTime());
    fileNameInput->setText(dateTime->toString().replace(" ","_"));
    delete dateTime;
    stateLabel.setGeometry(840,80,400,200);
    saveFileButton->setGeometry(840,200,100,30);
    fileName.setGeometry(950,200,70,30);
    fileNameInput->setGeometry(1040,200,200,30);
    saveFileButton->setEnabled(false);//默认无法保存文件
    //fileNameInput->setText();
    /*初始化相关数据*/
    current=0;
    /*申请必要内存空间*/
    points=new Point*[num];
    distance=new int[num];
    stage=0;
    connect(saveFileButton,SIGNAL(clicked(bool)),this,SLOT(saveGraphToFile()));




    update();
}
/**
 * @brief The Node struct 结构体，用于缓存文字信息，之后一次性描绘
 */
struct Node
{
    int x;
    int y;
    QString text;
    Node(){}

    Node(int x,int y,QString text)
    {
        this->x=x;
        this->y=y;
        this->text=text;    //qDebug()<<"distance当前指针的地址为"<<distance<<endl;
    }
};

/**
 * @brief calcVertexes 用于计算箭头坐标
 * @param start_x   起始横坐标
 * @param start_y   起始纵坐标
 * @param end_x 结束横坐标
 * @param end_y 结束纵坐标
 * @param x1    箭头1横坐标
 * @param y1    箭头1纵坐标
 * @param x2    箭头2横坐标
 * @param y2    箭头2纵坐标
 */
void calcVertexes(double start_x, double start_y, double end_x, double end_y, double& x1, double& y1, double& x2, double& y2)
{
        double arrow_lenght_ = 10;//箭头长度，一般固定
        double arrow_degrees_ = 0.5;//箭头角度，一般固定

        double angle = atan2(end_y - start_y, end_x - start_x) + 3.1415926;//

        x1 = end_x + arrow_lenght_ * cos(angle - arrow_degrees_);//求得箭头点1坐标
        y1 = end_y + arrow_lenght_ * sin(angle - arrow_degrees_);
        x2 = end_x + arrow_lenght_ * cos(angle + arrow_degrees_);//求得箭头点2坐标
        y2 = end_y + arrow_lenght_ * sin(angle + arrow_degrees_);
}

/**
 * @brief drawArray 画箭头
 * @param painter 画板
 * @param start 起点
 * @param end 终点
 */
void drawArrow(QPainter * painter,QPoint start,QPoint end)
{
    painter->drawLine(start,end);
    double x1, y1, x2, y2; //箭头的两点坐标
    //求得箭头两点坐标
    calcVertexes(start.x(), start.y(), end.x(), end.y(), x1, y1, x2, y2);
    painter->drawLine(end.x(), end.y(), x1, y1);//绘制箭头一半
    painter->drawLine(end.x(), end.y(), x2, y2);//绘制箭头另一半
}

/**
 * @brief drawArray 画箭头
 * @param graph 图指针
 * @param painter 画板
 * @param start 起点
 * @param end 终点
 */
void drawArrow(Graph * graph,QPainter * painter,int start,int end)
{
    drawArrow(painter,graph->getPoints()[start]->locate,graph->getPoints()[end]->locate);
}
/**
 * @brief calcLength 计算两个QPoint的距离
 * @param p1 QPoint1
 * @param p2 QPoint2
 * @return
 */
int calcLength(QPoint p1,QPoint p2)
{
    return (int)sqrt((p1.x()-p2.x())*(p1.x()-p2.x())+(p1.y()-p2.y())*(p1.y()-p2.y()));
}

void Graph::paintEvent(QPaintEvent *)
{
    QStack<Node> nodes;//用于保存所有编号
    /*信息区的文字更新*/
    switch (stage) {
    case 0:
        //stateLabel.setText(QString("当前阶段：标记点 左键标记，右键撤销,剩余%1个\n中键进入下一阶段").arg(num-current));
        stateLabel.setText(QString("当前阶段：标记点 左键标记，右键撤销,剩余%1个\n中键进入下一阶段").arg(num-current));
        break;
    case 1:
        if(current==-1)
            stateLabel.setText(QString("当前阶段：标记路线，标记路线,左键选择起点,中键\n结束连线阶段"));
        else
            stateLabel.setText(QString("当前阶段：标记路线，标记路线,当前选择起点%1,\n左键选择终点,右键取消选择").arg(current));
        break;
    case 2:
        if(current==-1)
            stateLabel.setText(QString("当前阶段:选择源点 左键选择起点"));
        else
            stateLabel.setText(QString("当前阶段:选择源点 已选择点%1，按中键表示确认").arg(current));
        break;
    case 3:
            stateLabel.setText(QString("当前阶段:演示阶段 左键前进，右键撤销，中键进入下一阶段"));
        break;
    case 4:
            stateLabel.setText(QString("当前阶段:路径显示，点击点显示源点到该点最短路径\n中键退出程序"));
        break;
    default:
        break;
    }
    QPainter * painter=new QPainter;
    painter->begin(this);
    /*绘制分割线*/
    painter->drawLine(QPoint(800,0),QPoint(800,800));
    painter->drawLine(QPoint(800,300),QPoint(1500,300));
    /*首先渲染点*/
    for(int i=0;i<(stage==0?current:num);i++)  //绘制所有点
    {
       nodes.push(Node(points[i]->locate.x()-RADIUM*2,points[i]->locate.y()-RADIUM*2,QString("%1").arg(i)));   //把点编号压栈
       painter->setBrush(QBrush(points[i]->color));//设置颜色
       painter->drawEllipse(points[i]->locate.x()-RADIUM,points[i]->locate.y()-RADIUM,RADIUM*2,RADIUM*2);//绘制实心点
    }
    /*渲染所有箭头*/
    for(int i=0;i<arrowList.size();i++)
    {
        QPen p;
        p.setColor(arrowList[i].color);
        p.setWidth(5);
        painter->setPen(p);
        drawArrow(this,painter,arrowList[i].start,arrowList[i].end);
        if(arrowList[i].length>0)
            nodes.push(Node((points[arrowList[i].start]->locate.x()+points[arrowList[i].end]->locate.x())/2,(points[arrowList[i].start]->locate.y()+points[arrowList[i].end]->locate.y())/2,QString("%1").arg(arrowList[i].length)));//长度压栈
    }
    /*统一绘制编号以及长度，因为比较容易被线段等覆盖*/
    QPen p;
    p.setColor(Qt::black);
    painter->setPen(p);
    while(!nodes.empty())
    {
        Node temp=nodes.pop();
        painter->drawText(temp.x,temp.y,temp.text);
    }
    /*在第四、五阶段时绘制解答信息*/
    if(stage==3||stage==4)
    {
        if(stage==3)
            painter->drawText(840,410,QString("源点%1到各点距离").arg(start));
        else
            painter->drawText(840,410,QString("源点%1到点%2的最短距离为%3").arg(start).arg(current).arg(distance[current]==INF?"∞":QString("%1").arg(distance[current])));
        for(int i=0;i<num;i++)
        {
            painter->drawText(840+i%10*40,500+i/10*30,QString("To %1").arg(i));
            painter->drawText(840+i%10*40,515+i/10*30,QString("%1").arg(distance[i]>=INF?"∞":QString("%1").arg(distance[i])));
        }
    }
    painter->end();
    delete painter;
}

bool Graph::canBePut(int x,int y)
{
    for(int i=0;i<(stage==0?current:num);i++)
    {
        if((points[i]->locate.x()-x)*(points[i]->locate.x()-x)+(points[i]->locate.y()-y)*(points[i]->locate.y()-y)<=4*RADIUM*RADIUM+50*RADIUM)
            return false;
    }
    return true;
}

int Graph::isExist(const int &x, const int &y)
{
    for(int i=0;i<(stage==0?current:num);i++)
    {
       if((points[i]->locate.x()-x)*(points[i]->locate.x()-x)+(points[i]->locate.y()-y)*(points[i]->locate.y()-y)<=RADIUM*RADIUM)
           return i;
    }
    return -1;
}

void Graph::renderPaint()
{
    while(!arrowList.empty())   //将历史笔迹清除
    {
        arrowList.pop_back();
    }

    //TODO 定义抽象函数让子类实现
    putArrow();
    switch(stage)
    {
        case 2:
                for(int i=0;i<num;i++)
                    points[i]->color=Qt::red;
                if(current!=-1)
                points[current]->color=Qt::green;
            break;
        case 3:
            for(int i=0;i<num;i++)
                distance[i]=model->distance[i];
            for(int i=0;i<num;i++)
                points[i]->color=model->flag[i]?Qt::green:Qt::red;
            if(model->current!=-1)
                points[model->current]->color=Qt::yellow;
            /*绘制路线*/
            if(model->solvePath.size()>1)
            {
                for(int i=1;i<model->solvePath.size();i++)
                {
                    for(int j=1;j<model->path[model->solvePath[i]].size();j++)
                    {
                    arrowList.push_back(Arrow(model->path[model->solvePath[i]][j-1],model->path[model->solvePath[i]][j],0,Qt::blue));
                    }
                }
            }
            break;
        case 4:
            for(int i=0;i<num;i++)
                points[i]->color=Qt::green;
            if(current!=-1)
            {
                points[current]->color=Qt::black;
                for(int j=1;j<model->path[current].size();j++)
                {
                arrowList.push_back(Arrow(model->path[current][j-1],model->path[current][j],0,Qt::blue));
                }
            }
            break;

    }

    update();
}

bool Graph::leftEvent(QMouseEvent * event)
{
    /*如果不是第一、五阶段首先将当前模型入栈*/
    if(stage!=0&&stage!=4)
    {
        //TODO 定义抽象函数 保存当前模型
        saveModel();
    }
    int end;//用于保存终点
    switch (stage)
    {  //不同阶段完成不同的事情
    case 0: //选点阶段
        if(current<num&&canBePut(event->x(),event->y()))//如果可以放置新的点
        {
            points[current++]=new Point(QPoint(event->x(),event->y()),Qt::red);//为点数组添加一个新的点
            renderPaint();
        }
        return false;//因为这个操作没有影响到模型，所以看做无效操作
    case 1: //连线阶段
        if(current==-1) //如果当前没有选择起点
        {
            current=isExist(event->x(),event->y());
            renderPaint();
            return false;//选择起点的过程不需要入栈,但是需要重绘
        }
        else
        {
            end=isExist(event->x(),event->y());//获取终点坐标
            if(end==-1)//如果没有选择到点
            {
                return false;//返回
            }
            if(end==current)
            {
                QMessageBox::critical(this,"错误","不得自成环");     //给出错误提示
                return false;         //返回
            }
            model->addEdge(current,end,(int)sqrt((points[current]->locate.x()-points[end]->locate.x())*(points[current]->locate.x()-points[end]->locate.x())+
                    (points[current]->locate.y()-points[end]->locate.y())*(points[current]->locate.y()-points[end]->locate.y())));
            current=-1;
        }
        break;
    case 2: //源点选取阶段
        end=isExist(event->x(),event->y());//获取源点坐标
        if(end==-1)//如果采了无效点
            return false;//返回，避免无效片段入栈
        current=end;
        break;
    case 3:     //寻路阶段
        if(model->finish)
        {
            QMessageBox::critical(this,"错误","已找到全部可达路径");     //给出错误提示
            return false;
        }
        if(!model->step())
        {
            model->finish=true;
            model->current=-1;
        }
        break;
    case 4:
        end=isExist(event->x(),event->y());//获取源点坐标
        if(end!=-1)//如果不是无效点
            current=end;;//返回，避免无效片段入栈
        renderPaint();
        return false;
    default:
        break;
    }
    return true;
}
void Graph::rightEvent()
{
    if(stage==0&&current>0)//如果是第一阶段，还没有模型参与，所以只需要简单减少一个点即可
    {
        current--;
    }
    if(stage==1&&current!=-1)//如果是连线阶段并且找终点
    {
        current=-1; //先找起点
        return;
    }
    if(stage!=0&&!history.empty())
    {
        delete model;
        model=history.pop();
    }
}

bool Graph::midEvent()
{
    switch (stage)  //不同阶段对于能否进入下一阶段有不同的评判标准
    {
    case 0:
        //第一阶段，需要查看是否标记了所有点的坐标
        if(current<num)    //如果全部点还未标记好
        {
            QMessageBox::critical(this,"错误！","点没有标记完毕！");
            return false;
        }
        current=-1;
        break;
    case 2:     //确定源点后填充distance数组
        if(current==-1)
            return false;
        model->distance[current]=0;
        start=current;
        model->flag[current]=true;
        model->current=current;//设置模型当前点
        model->solvePath.push_back(current);
        for(int i=0;i<model->num;i++)
        {
            model->path[i].push_back(i);//为每条路径推入自身
        }
        current=0;
        saveFileButton->setEnabled(true);
        break;

    case 3:
        if(!model->finish)
        {
            QMessageBox::critical(this,"错误！","寻路阶段未结束！！");
            return false;
        }
        current=start;
        break;
    case 4:
        close();
        break;
    default:
        break;
    }
    while(!history.empty())
    {
        delete history.pop();
    }
    stage++;
    return true;
}

void Graph::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->x()>800)  //如果点击了状态区
        return; //返回
    if(event->button()==Qt::RightButton)// 如果点击了右键
    {
        rightEvent();//调用右键事件
    }
    else if(event->button()==Qt::MidButton)
    {
        if(!midEvent())//如果调用中键事件无效
        {
            return;
        }
    }
    else
    {
        if(!leftEvent(event))//如果调用左键事件无效
        {
            if(stage!=0&&stage!=4)
            {
                delete model;
                model=history.pop();
            }
            return;
        }
    }
    renderPaint();
}

void Graph::getRandomMap()
{
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));  //设定种子
    for(int i=0;i<num;i++)  //首先生成点
    {
        int x=qrand()%(800-2*RADIUM)+RADIUM;    //随机产生x,y
        int y=qrand()%(800-2*RADIUM)+RADIUM;
        if(!canBePut(x,y))  //如果不能放
        {
            i--;
            continue;
        }
        points[i]=new Point(QPoint(x,y),Qt::red);   //添加新点
    }
    for(int i=0;i<num*2;i++)//连线
    {
        int start=qrand()%num;  //随机生成起点
        int end=qrand()%num;    //随机生成终点
        if(start==end)      //如果起点等于终点
        {
            i--;        //重来
            continue;
        }
        model->addEdge(start,end,(int)sqrt((points[start]->locate.x()-points[end]->locate.x())*(points[start]->locate.x()-points[end]->locate.x())+(points[start]->locate.y()-points[end]->locate.y())*(points[start]->locate.y()-points[end]->locate.y())));
    }
    current=0;
    stage=2;
}

void Graph::saveGraphToFile()
{
    QString fileName=fileNameInput->text().append(".txt");
    //qDebug()<<"保存为"<<fileName;
    QFile graph(fileName);
    if(graph.open(QFile::WriteOnly|QFile::Truncate)){
        QTextStream out(&graph);
        out<<"Z_GRAPH"<<endl;//表明是该程序可以读取的文件
        out<<num<<endl;//输入点个数
        for(int i=0;i<num;i++)  //依次输入点信息
        {
            out<<i<<"\t"<<points[i]->locate.x()<<"\t"<<points[i]->locate.y()<<endl;
        }
        //TODO 定义接口交给子类实现
        saveEdge(out);
    }
}

void Graph::loadGraphFromFile()
{
    //qDebug()<<"载入文件";
    QString fileName;
    bool loop=true;

    while(loop)
    {
        fileName=QFileDialog::getOpenFileName(this,"选择载入文件，取消则手动选择","./","txt文件(*.txt)");
        if(fileName=="")
            return;
        QFile file(fileName);
        QString sure;
        if(file.open(QFile::ReadOnly | QFile::Text))//如果成功打开了文件
        {
            QTextStream in(&file);
            sure=in.readLine();
            if(sure=="Z_GRAPH") //如果是可读图文件
            {
                loop=false;//跳出循环
                /*进入读图阶段*/
                num=in.readLine().toInt();
                //qDebug()<<num;
                delete points;
                delete distance;
                points=new Point*[num];
                distance=new int[num];
                for(int i=0;i<num;i++)
                {
                    QList<QString> temp=in.readLine().split("\t"); //读取一行
                    //qDebug()<<temp.at(1)<<" "<<temp.at(2);
                    points[temp.at(0).toInt()]=new Point(QPoint(temp.at(1).toInt(),temp.at(2).toInt()),Qt::red);
                }
                while(!in.atEnd())
                {
                    QList<QString> temp=in.readLine().split("\t"); //读取一行
                    int p1=temp.at(0).toInt(),p2=temp.at(1).toInt();
                    model->addEdge(p1,p2,calcLength(points[p1]->locate,points[p2]->locate));
                }
            }
            else
            {
                QMessageBox::critical(this,"错误！","不是可读图文件！！");
                continue;
            }
        }
    }
    stage=2;
}

Graph::~Graph()
{
   delete[] distance;
   for(int i=0;i<(stage==0?current:num);i++)
       delete points[i];
   delete[] points;
}
